
#pragma once

#include <spdlog/spdlog.h>
#include <vsg/all.h>
#include <vsgQt/Window.h>

namespace qbe::render::tools {

static vsgQt::Window* createWindow(vsg::ref_ptr<vsgQt::Viewer> viewer, vsg::ref_ptr<vsg::WindowTraits> traits, vsg::ref_ptr<vsg::Node> vsg_scene,
                                   QWindow* parent, const QString& title = {}) {

  auto window = new vsgQt::Window(viewer, traits, parent);
  window->setTitle(title);
  window->initializeWindow();

  // if this is the first window to be created, use its device for future window creation.
  if (!traits->device)
    traits->device = window->windowAdapter->getOrCreateDevice();

  SPDLOG_INFO("traits width: {}, height: {}", traits->width, traits->height);
  SPDLOG_INFO("window width: {}, height: {}", window->traits->width, window->traits->height);

  uint32_t width = window->traits->width;
  uint32_t height = window->traits->height;
  auto width_half = static_cast<double>(width / 2);
  auto height_half = static_cast<double>((height) / 2);

  auto viewport = vsg::ViewportState::create(0, 0, width, height);
  auto ortho = vsg::Orthographic::create(-width_half, width_half, -height_half, height_half, 0.0f, 10.0f);
  auto lookAt = vsg::LookAt::create(vsg::dvec3(0.0, 0.0, 5.0), vsg::dvec3(0.0, 0.0, 0.0), vsg::dvec3(0.0, 1.0, 0.0));
  auto camera = vsg::Camera::create(ortho, lookAt, viewport);
  auto commandGraph = vsg::createCommandGraphForView(*window, camera, vsg_scene);
  viewer->addRecordAndSubmitTaskAndPresentation({commandGraph});
  return window;
}

static vsgQt::Window* createOrthoCamera(vsg::ref_ptr<vsgQt::Viewer> viewer, vsg::ref_ptr<vsg::WindowTraits> traits, vsg::ref_ptr<vsg::Node> vsg_scene,
                                        QWindow* parent, const QString& title = {}) {

  auto window = new vsgQt::Window(viewer, traits, parent);
  window->setTitle(title);
  window->initializeWindow();

  // if this is the first window to be created, use its device for future window creation.
  if (!traits->device)
    traits->device = window->windowAdapter->getOrCreateDevice();

  SPDLOG_INFO("traits width: {}, height: {}", traits->width, traits->height);
  SPDLOG_INFO("window width: {}, height: {}", window->traits->width, window->traits->height);

  uint32_t width = window->traits->width;
  uint32_t height = window->traits->height;
  auto width_half = static_cast<double>(width / 2);
  auto height_half = static_cast<double>((height) / 2);

  auto viewport = vsg::ViewportState::create(0, 0, width, height);
  auto ortho = vsg::Orthographic::create(-width_half, width_half, -height_half, height_half, 0.0f, 10.0f);
  auto lookAt = vsg::LookAt::create(vsg::dvec3(0.0, 0.0, 5.0), vsg::dvec3(0.0, 0.0, 0.0), vsg::dvec3(0.0, 1.0, 0.0));
  auto camera = vsg::Camera::create(ortho, lookAt, viewport);
  auto commandGraph = vsg::createCommandGraphForView(*window, camera, vsg_scene);
  viewer->addRecordAndSubmitTaskAndPresentation({commandGraph});
  return window;
}

template <typename T>
static vsg::ref_ptr<vsg::Node> createStateNode(vsg::ref_ptr<vsg::Options> options, vsg::Path vert_path, vsg::Path frag_path, T uniform,
                                               VkPrimitiveTopology primitiveTopology, bool instance) {

  vsg::ref_ptr<vsg::ShaderStage> vertexShader = vsg::ShaderStage::read(VK_SHADER_STAGE_VERTEX_BIT, "main", vsg::findFile(vert_path, options));
  vsg::ref_ptr<vsg::ShaderStage> fragmentShader = vsg::ShaderStage::read(VK_SHADER_STAGE_FRAGMENT_BIT, "main", vsg::findFile(frag_path, options));
  if (!vertexShader || !fragmentShader) {
    SPDLOG_INFO("Could not create shaders.");
    assert(0 && "Shader file not find");
    return nullptr;
  }

  // Uniform绑定
  vsg::DescriptorSetLayoutBindings descriptorBindings{
      {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT, nullptr},  // ClipSettings uniform
      // { binding, descriptorType, descriptorCount, stageFlags, pImmutableSamplers}
  };

  auto descriptorSetLayout = vsg::DescriptorSetLayout::create(descriptorBindings);

  // projection, view, and model matrices, actual push constant calls automatically provided by the VSG's RecordTraversal
  vsg::PushConstantRanges pushConstantRanges{{VK_SHADER_STAGE_VERTEX_BIT, 0, 128}};

  vsg::VertexInputState::Bindings vertexBindingsDescriptions = vsg::VertexInputState::Bindings{
      VkVertexInputBindingDescription{0, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX},  // vertex data
  };
  vsg::VertexInputState::Attributes vertexAttributeDescriptions = vsg::VertexInputState::Attributes{
      VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0},  // vertex data
  };

  if (instance) {
    vertexBindingsDescriptions = vsg::VertexInputState::Bindings{
        VkVertexInputBindingDescription{0, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX},    // vertex data
        VkVertexInputBindingDescription{1, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_INSTANCE},  // instance tran
        VkVertexInputBindingDescription{2, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_INSTANCE},  // instance mxr
        VkVertexInputBindingDescription{3, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_INSTANCE},  // instance mxr
    };
    vertexAttributeDescriptions = vsg::VertexInputState::Attributes{
        VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0},  // vertex data
        VkVertexInputAttributeDescription{1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0},  // instance tran
        VkVertexInputAttributeDescription{2, 2, VK_FORMAT_R32G32B32_SFLOAT, 0},  // instance mxr
        VkVertexInputAttributeDescription{3, 3, VK_FORMAT_R32G32B32_SFLOAT, 0},  // instance mxr
    };
  }

  // primitiveTopology = VkPrimitiveTopology::VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
  // primitiveTopology = VkPrimitiveTopology::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;

  // 图元重启默认开启
  VkBool32 primitiveRestart = VK_TRUE;

  vsg::GraphicsPipelineStates pipelineStates{vsg::VertexInputState::create(vertexBindingsDescriptions, vertexAttributeDescriptions),
                                             vsg::InputAssemblyState::create(primitiveTopology, primitiveRestart),
                                             vsg::RasterizationState::create(),
                                             vsg::MultisampleState::create(),
                                             vsg::ColorBlendState::create(),
                                             vsg::DepthStencilState::create()};

  auto pipelineLayout = vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout}, pushConstantRanges);
  auto graphicsPipeline = vsg::GraphicsPipeline::create(pipelineLayout, vsg::ShaderStages{vertexShader, fragmentShader}, pipelineStates);
  auto bindGraphicsPipeline = vsg::BindGraphicsPipeline::create(graphicsPipeline);

  // create UniformValue and associated DescriptorSets and binding
  auto uniformBuffer = vsg::DescriptorBuffer::create(uniform, 0);
  auto descriptorSet = vsg::DescriptorSet::create(descriptorSetLayout, vsg::Descriptors{uniformBuffer});
  auto bindDescriptorSet = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, descriptorSet);

  // create StateGroup as the root of the scene/command graph to hold the GraphicsPipeline, and binding of Descriptors to decorate the whole graph
  auto stateGroup = vsg::StateGroup::create();
  stateGroup->add(bindGraphicsPipeline);
  stateGroup->add(bindDescriptorSet);

  return stateGroup;
}

static vsg::ref_ptr<vsg::Commands> createCommands(vsg::ref_ptr<vsg::vec2Array> vertices, vsg::ref_ptr<vsg::vec3Array> colors,
                                                  vsg::ref_ptr<vsg::uintArray> indices) {
  auto drawCommands = vsg::Commands::create();
  auto vert_buf = vsg::BindVertexBuffers::create(0, vsg::DataList{vertices, colors});
  auto idx_buf = vsg::BindIndexBuffer::create(indices);
  auto draw_idx = vsg::DrawIndexed::create(indices->size(), 1, 0, 0, 0);
  drawCommands->addChild(vert_buf);
  drawCommands->addChild(idx_buf);
  drawCommands->addChild(draw_idx);
  return drawCommands;
}

static vsg::ref_ptr<vsg::Commands> createCommands(vsg::ref_ptr<vsg::vec2Array> vertices, vsg::ref_ptr<vsg::uintArray> indices) {

  auto drawCommands = vsg::Commands::create();
  drawCommands->addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{vertices}));
  drawCommands->addChild(vsg::BindIndexBuffer::create(indices));
  drawCommands->addChild(vsg::DrawIndexed::create(indices->size(), 1, 0, 0, 0));
  return drawCommands;
}

static vsg::ref_ptr<vsg::Commands> createInstanceCommandsMat3(vsg::ref_ptr<vsg::vec2Array> vertices, vsg::ref_ptr<vsg::uintArray> indices,
                                                              vsg::ref_ptr<vsg::vec3Array> instance_v1, vsg::ref_ptr<vsg::vec3Array> instance_v2,
                                                              vsg::ref_ptr<vsg::vec3Array> instance_v3, uint32_t instance_count) {
  auto drawCommands = vsg::Commands::create();
  drawCommands->addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{vertices, instance_v1, instance_v2, instance_v3}));
  drawCommands->addChild(vsg::BindIndexBuffer::create(indices));
  drawCommands->addChild(vsg::DrawIndexed::create(indices->size(), instance_count, 0, 0, 0));
  return drawCommands;
}

static vsg::ref_ptr<vsg::Commands> createInstanceCommandsMat4(vsg::ref_ptr<vsg::vec2Array> vertices, vsg::ref_ptr<vsg::uintArray> indices,
                                                              vsg::ref_ptr<vsg::vec4Array> instance_v1, vsg::ref_ptr<vsg::vec4Array> instance_v2,
                                                              vsg::ref_ptr<vsg::vec4Array> instance_v3, vsg::ref_ptr<vsg::vec4Array> instance_v4,
                                                              uint32_t instance_count) {
  auto drawCommands = vsg::Commands::create();
  drawCommands->addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{vertices, instance_v1, instance_v2, instance_v3, instance_v4}));
  drawCommands->addChild(vsg::BindIndexBuffer::create(indices));
  drawCommands->addChild(vsg::DrawIndexed::create(indices->size(), instance_count, 0, 0, 0));
  return drawCommands;
}

static void nodeClearChildren(vsg::ref_ptr<vsg::Node> node) {
  if (node) {
    auto node_ptr = dynamic_cast<vsg::StateGroup*>(node.get());
    node_ptr->children.clear();
  }
}

}  // namespace qbe::render::tools